package util;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.Vector;

import javax.microedition.rms.InvalidRecordIDException;
import javax.microedition.rms.RecordComparator;
import javax.microedition.rms.RecordEnumeration;
import javax.microedition.rms.RecordFilter;
import javax.microedition.rms.RecordStore;
import javax.microedition.rms.RecordStoreException;
import javax.microedition.rms.RecordStoreFullException;
import javax.microedition.rms.RecordStoreNotFoundException;
import javax.microedition.rms.RecordStoreNotOpenException;

public class Storage implements RecordComparator, RecordFilter {
	int[] allRecordIDs;

	String databaseName;

	String errorText = "";

	long lastTimeStamp;

	String matchString = null;

	int recordID = -1; // default record for single setText and getText

	RecordStore rs = null;

	boolean useTimeStamp = false;

	/**
	 * Returns information about a record store
	 * 
	 * @param _name
	 *            Which Recordstore
	 * @return three things in an array of longs, 0: date modified, 1: nubmer of records and 2: size and 3 sizeavailable
	 */
	public static long[] getInfo(String _name) {
		long[] result = new long[4];
		try {
			RecordStore thisLog = RecordStore.openRecordStore(_name, false);
			result[0] = thisLog.getLastModified();
			result[1] = thisLog.getNumRecords();
			result[2] = thisLog.getSize();
			result[3] = thisLog.getSizeAvailable();

		} catch (RecordStoreFullException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (RecordStoreNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (RecordStoreNotOpenException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (RecordStoreException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return result;
	}

	/**
	 * Static version of getRecord for convenience of people mostly using RecordStore (not wrapping it in my "Storage" class).
	 * 
	 * @param _myRecordStore
	 * @param _whichRecord
	 * @param _timestamp
	 * @return
	 */
	public static byte[] getRecord(RecordStore _myRecordStore, int _whichRecord, boolean _timestamp) {
		try {
			byte[] data = (byte[]) _myRecordStore.getRecord(_whichRecord);
			if (_timestamp) {
				stripTimeStamp(data);
			}
			return data;
		} catch (RecordStoreNotOpenException e) {
			System.out.println("error getting record static " + e.getMessage());
		} catch (InvalidRecordIDException e) {
			System.out.println("error getting record static " + e.getMessage());
		} catch (RecordStoreException e) {
			System.out.println("error getting record static " + e.getMessage());
		}
		return null;

	}

	public static long getTimeStamp(byte[] _rec) {

		long result = -1;

		try {
			ByteArrayInputStream bin = new ByteArrayInputStream(_rec);

			DataInputStream din = new DataInputStream(bin);
			result = din.readLong(); // peal off the timestamp
			din.close();
			bin.close();

		} catch (IOException e) {
			System.out.println(_rec + "error getting time stamp" + e.getMessage());
			return result;
		} catch (Exception e){
			System.out.println(_rec + "error getting time stamp" + e.getMessage());
			return result;
					}
		return result;
	}

	/**
	 * Static version of getTimeCode for convenience of people mostly using RecordStore (not wrapping it in my "Storage" class).
	 * 
	 * @param _myRecordStore
	 * @param _whichRecord
	 * @return the time stamp as a long
	 */
	public static long getTimeStamp(RecordStore _myRecordStore, int _whichRecord) {
		byte[] rec = null;
		try {
			rec = (byte[]) _myRecordStore.getRecord(_whichRecord);

		} catch (RecordStoreNotOpenException e) {
			System.out.println("error getting time stamp static " + e.getMessage());
		} catch (InvalidRecordIDException e) {
			System.out.println("error getting time stamp static " + e.getMessage());
		} catch (RecordStoreException e) {
			System.out.println("error getting time stamp static " + e.getMessage());
		}

		return getTimeStamp(rec);
	}

	public static String[] split(String input, char delim) {

		String textsofar = "";
		Vector rVector = new Vector();
		for (int i = 0; i < input.length(); i++) {
			char c = input.charAt(i);
			if (c == delim) {
				rVector.addElement(textsofar);
				textsofar = "";
			} else {
				textsofar = textsofar + (char) c;
			}
		}
		rVector.addElement(textsofar);
		String[] stringArray = new String[rVector.size()];
		rVector.copyInto(stringArray);
		return stringArray;
	}

	public static byte[] stripTimeStamp(byte[] _rec) {
		//
		ByteArrayInputStream bin = new ByteArrayInputStream(_rec);
		DataInputStream din = new DataInputStream(bin);

		byte[] data;
		try {
			din.readLong(); // peal off the timestamp
			int size = din.readInt();
			data = new byte[size]; // read to the end
			din.readFully(data);
			din.close();
			bin.close();
			return data;
			// } catch (EOFException e) {
			// System.out.println("Probably looking for a time stamp on a record that does not have one");
		} catch (IOException e) {
			// TODO Auto-generated catch block
			System.out.println("Maybe looking for a time stamp on a record that does not have one");

			e.printStackTrace();
		}
		return null;
	}

	public Storage(String _storageName) {
		databaseName = _storageName;
		try {
			// don't make names too big
			String[] names = split(_storageName, '.');
			if (names.length > 2)
				_storageName = names[names.length - 2] + "." + names[names.length - 1];
			int start = Math.max(_storageName.length() - 31, 0);

			_storageName = _storageName.substring(start);

			rs = RecordStore.openRecordStore(_storageName, true);// ,RecordStore.AUTHMODE_ANY,true
			// rs = RecordStore.openRecordStore(_storageName, true, RecordStore.AUTHMODE_ANY,true);
			allRecordIDs = getRecordIDs();
			// for simple setText and getText
			if (allRecordIDs != null && allRecordIDs.length > 0) {
				recordID = allRecordIDs[0];
			}
		} catch (RecordStoreFullException e) {
			errorText = (e.getMessage() + "RecordStoreFull" + recordID);
		} catch (RecordStoreException e) {
			errorText = (e.getMessage() + "RecordStore" + recordID);
		} catch (Exception e) {
			errorText = (e.getMessage() + "AnotherError" + recordID);

		}
	}

	public int addRecord(byte[] _contents) {
		int returnedRecordID = -1;
		if (useTimeStamp) {
			_contents = addTimeStampToRecord(_contents);
		}

		try {
			returnedRecordID = rs.addRecord(_contents, 0, _contents.length);

		} catch (RecordStoreNotOpenException e) {
			errorText = (e.getMessage() + "RecordStoreNotOpen" + recordID);
		} catch (RecordStoreFullException e) {
			errorText = (e.getMessage() + "RecordStoreFull" + recordID);
		} catch (RecordStoreException e) {
			errorText = (e.getMessage() + "RecordStore" + recordID);
		}
		allRecordIDs = getRecordIDs();

		return returnedRecordID;

	}

	public int addRecord(byte[] _rec, byte[] _metadata) {
		ByteArrayOutputStream bout = new ByteArrayOutputStream();
		DataOutputStream dout = new DataOutputStream(bout);

		try {
			if (useTimeStamp) {
				dout.writeLong(System.currentTimeMillis());
			}
			dout.writeInt(_rec.length);
			dout.write(_rec);
			if (_metadata != null) {
				dout.writeInt(_metadata.length);
				dout.write(_metadata);
			}
			dout.flush();
			dout.close();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		byte[] data = bout.toByteArray();
		try {
			recordID = rs.addRecord(data, 0, data.length);

		} catch (RecordStoreNotOpenException e) {
			errorText = (e.getMessage() + "RecordStoreNotOpen" + recordID);
		} catch (RecordStoreFullException e) {
			errorText = (e.getMessage() + "RecordStoreFull" + recordID);
		} catch (RecordStoreException e) {
			errorText = (e.getMessage() + "RecordStore" + recordID);
		}
		allRecordIDs = getRecordIDs();

		return recordID;

	}

	public int addRecord(String _contents) {
		return addRecord(_contents.getBytes());
	}

	public byte[] addTimeStampToRecord(byte[] _bytes) {
		ByteArrayOutputStream bout = new ByteArrayOutputStream();
		DataOutputStream dout = new DataOutputStream(bout);

		try {

			dout.writeLong(System.currentTimeMillis());
			dout.writeInt(_bytes.length);
			dout.write(_bytes);
			dout.flush();
			dout.close();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		byte[] data = bout.toByteArray();

		return data;
	}

	public byte[] appendInt(byte[] _existing, int _int) {
		ByteArrayOutputStream bout = new ByteArrayOutputStream();
		DataOutputStream dout = new DataOutputStream(bout);
		try {
			dout.write(_existing);
			dout.writeInt(_int);
			dout.flush();
			dout.close();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		byte[] data = bout.toByteArray();
		return data;
	}

	public void appendMetadata(int _returnedRecordNumber, byte[] _metadata) {
		byte[] existing = null;
		ByteArrayOutputStream bout = new ByteArrayOutputStream();
		DataOutputStream dout = new DataOutputStream(bout);
		try {
			existing = rs.getRecord(_returnedRecordNumber);

			try {
				dout.write(existing);
				dout.writeInt(_metadata.length);
				dout.write(_metadata);

				dout.flush();
				dout.close();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}

		} catch (RecordStoreFullException e) {
			errorText = (e.getMessage() + "RecordStoreFull" + _returnedRecordNumber);
		} catch (RecordStoreNotFoundException e) {
			errorText = (e.getMessage() + "RecordStoreNotFound" + _returnedRecordNumber);
		} catch (RecordStoreException e) {
			errorText = (e.getMessage() + "RecordStore" + _returnedRecordNumber);
		}

		byte[] data = bout.toByteArray();
		setRecord(_returnedRecordNumber, data);
	}

	public void clearRecords() {
		// int[] allRecordsIDs = getRecordIDs();
		recordID = -1;
		for (int i = 0; i < allRecordIDs.length; i++) {
			try {
				rs.deleteRecord(allRecordIDs[i]);
			} catch (RecordStoreNotOpenException e) {
				errorText = (e.getMessage());
			} catch (InvalidRecordIDException e) {
				errorText = (e.getMessage());
				;
			} catch (RecordStoreException e) {
				errorText = (e.getMessage());
			}
		}
	}

	/*
	 * RecordComparator interface
	 */
	public int compare(byte[] rec1, byte[] rec2) {
		if (rec1 == null || rec2 == null) return RecordComparator.FOLLOWS;
		try {
			if (isTimeSTamping()) {
				long val1 = getTimeStamp(rec1);
				long val2 = getTimeStamp(rec2);
				if (val1 == -1 || val2 == -1)return RecordComparator.EQUIVALENT;
				if (val1 == val2)
					return RecordComparator.EQUIVALENT;
				else if (val1 < val2)
					return RecordComparator.FOLLOWS;
				else
					return RecordComparator.PRECEDES;

			} else {
				return RecordComparator.EQUIVALENT;
			
			}
		} catch (Exception e) {
			errorText = (e.getMessage() + "Compare" + rec1 + " " + rec2);
			return RecordComparator.FOLLOWS;
		}
		
		
	}

	public void deleteMe() {
		try {
			boolean bCouldBeOpen = true;
			while (bCouldBeOpen) {
				try {
					rs.closeRecordStore();
				} catch (RecordStoreNotOpenException e) {
					// we know it isn't open now!
					bCouldBeOpen = false;
				}
			}

			RecordStore.deleteRecordStore(databaseName);

		} catch (RecordStoreNotFoundException e) {
			errorText = (e.getMessage());
		} catch (RecordStoreException e) {
			errorText = (e.getMessage());
		}
	}

	public void deleteRecord(int _whichone) {

		try {
			rs.deleteRecord(allRecordIDs[_whichone]);
		} catch (RecordStoreNotOpenException e) {
			errorText = (e.getMessage());
		} catch (InvalidRecordIDException e) {
			errorText = (e.getMessage());
			;
		} catch (RecordStoreException e) {
			errorText = (e.getMessage());
		}
		allRecordIDs = getRecordIDs();
	}

	public void done() {
		try {
			boolean bCouldBeOpen = true;
			while (bCouldBeOpen) {
				try {
					rs.closeRecordStore();
				} catch (RecordStoreNotOpenException e) {
					// we know it isn't open now!
					bCouldBeOpen = false;
				}
			}

		} catch (RecordStoreNotFoundException e) {
			errorText = (e.getMessage());
		} catch (RecordStoreException e) {
			errorText = (e.getMessage());
		}

	}

	public String getLastError() {
		return errorText;
	}

	public String getName() {
		String result = null;
		try {
			result = rs.getName();
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return result;
	}

	public int getNumberOfRecords() {
		try {
			return rs.getNumRecords();
		} catch (RecordStoreNotOpenException e) {
			errorText = (e.getMessage() + "RecordStoreNotOpen" + recordID);
			return -1;
		}
	}

	/**
	 * @param _which
	 * @return
	 */
	public byte[] getRecord(int _which) {
		recordID = allRecordIDs[_which];
		byte[] contents = null;
		try {

			contents = rs.getRecord(recordID);
			if (useTimeStamp) {
				// System.out.println("BeforeTimeStamp" + new String(contents));
				contents = stripTimeStamp(contents);
				// System.out.println("AfterTimeStamp" + new String(contents));
			}

		} catch (RecordStoreFullException e) {
			errorText = (e.getMessage() + "RecordStoreFull" + recordID);
		} catch (RecordStoreNotFoundException e) {
			errorText = (e.getMessage() + "RecordStoreNotFound" + recordID);
		} catch (RecordStoreException e) {
			errorText = (e.getMessage() + "RecordStore" + recordID);
		}
		return contents;

	}

	/**
	 * Leave the timestamp as binary data in the time stamp
	 * 
	 * @param _which
	 * @return
	 */
	public byte[] getRecordAndStamp(int _which) {
		recordID = allRecordIDs[_which];
		byte[] contents = null;
		try {
			contents = rs.getRecord(recordID);
		} catch (RecordStoreFullException e) {
			errorText = (e.getMessage() + "RecordStoreFull" + recordID);
		} catch (RecordStoreNotFoundException e) {
			errorText = (e.getMessage() + "RecordStoreNotFound" + recordID);
		} catch (RecordStoreException e) {
			errorText = (e.getMessage() + "RecordStore" + recordID);
		}
		return contents;

	}

	// static for people who are not wrapping their Record store in the Storage just using the functions
	/*
	 * public static int[] getRecordIDs(RecordStore _rs) { int[] ids = null; try {
	 * 
	 * int numRecords = _rs.getNumRecords();
	 * 
	 * ids = new int[numRecords];
	 * 
	 * RecordEnumeration re = _rs.enumerateRecords(null, null, false); int which = numRecords - 1; while (re.hasNextElement()) { ids[which] = re.nextRecordId(); which--; } } catch (RecordStoreNotOpenException e) { System.out.println(e.getMessage() + "RecordStoreNotOpen"); } catch (InvalidRecordIDException e) { System.out.println(e.getMessage() + "InvalidRecordID"); } return ids; }
	 */
	public int[] getRecordIDs() {
		int[] ids = null;
		try {

			int numRecords = rs.getNumRecords();

			ids = new int[numRecords];
			// RecordEnumeration re = rs.enumerateRecords(null, null, false);
			RecordEnumeration re = rs.enumerateRecords(this, this, false);

			int which = numRecords - 1;
			while (re.hasNextElement()) {
				ids[which] = re.nextRecordId();
				which--;
			}
		} catch (RecordStoreNotOpenException e) {
			errorText = (e.getMessage() + "RecordStore" + recordID);
		} catch (InvalidRecordIDException e) {
			errorText = (e.getMessage() + "RecordStore" + recordID);
		}
		return ids;
	}

	public Vector getRecordParts(int _which) {
		//
		recordID = allRecordIDs[_which];
		byte[] contents = null;
		try {
			contents = rs.getRecord(recordID);
		} catch (RecordStoreFullException e) {
			errorText = (e.getMessage() + "RecordStoreFull" + recordID);
		} catch (RecordStoreNotFoundException e) {
			errorText = (e.getMessage() + "RecordStoreNotFound" + recordID);
		} catch (RecordStoreException e) {
			errorText = (e.getMessage() + "RecordStore" + recordID);
		}
		Vector returnParts = new Vector();
		ByteArrayInputStream bin = new ByteArrayInputStream(contents);
		DataInputStream din = new DataInputStream(bin);

		byte[] data;
		try {
			if (useTimeStamp) {
				long time = din.readLong(); // peal off the timestamp
				returnParts.addElement(new Long(time));
			} else {
				returnParts.addElement(new Long(0));// phony
			}
			int size = din.readInt();
			data = new byte[size]; // read to the end
			din.readFully(data);
			returnParts.addElement(data);
			// add metadata
			if (din.available() > 0) {
				size = din.readInt();
				data = new byte[size]; // read to the end
				din.readFully(data);
				returnParts.addElement(data);
			} else {
				returnParts.addElement(null);
			}
			returnParts.addElement(contents);
			din.close();
			bin.close();
			return returnParts;
			// } catch (EOFException e) {
			// System.out.println("Probably looking for a time stamp on a record that does not have one");
		} catch (IOException e) {
			// TODO Auto-generated catch block
			System.out.println("Maybe looking for a time stamp on a record that does not have one");

			e.printStackTrace();
		}
		return null;
	}

	public Vector getRecords() {
		Vector allRecords = new Vector();

		try {
			RecordEnumeration re = rs.enumerateRecords(null, null, false);
			while (re.hasNextElement()) {
				allRecords.addElement(re.nextRecord());
			}
		} catch (RecordStoreNotOpenException e) {
			errorText = (e.getMessage() + "RecordStoreNotOpen" + recordID);
		} catch (InvalidRecordIDException e) {
			errorText = (e.getMessage() + "InvalidRecordID" + recordID);
		} catch (RecordStoreException e) {
			errorText = (e.getMessage() + "RecordStoreException" + recordID);
		}
		return allRecords;
	}

	public RecordStore getRecordStore() {
		return rs;
	}

	public String getText() {
		byte[] bytes = "Empty".getBytes();

		if (recordID != -1) {
			try {

				bytes = rs.getRecord(recordID);
				if (bytes == null)
					bytes = "Empty".getBytes();

			} catch (RecordStoreNotOpenException e) {
				errorText = (e.getMessage() + "RecordStoreNotOpen" + recordID);
			} catch (InvalidRecordIDException e) {
				errorText = (e.getMessage() + "InvalidRecordID" + recordID);
			} catch (RecordStoreException e) {
				errorText = (e.getMessage() + "RecordStore" + recordID);
			}
		}

		return new String(bytes);
	}

	/**
	 * Strips the timestamp off the front of the record
	 * 
	 * @param _whichOne
	 *            specifies which record
	 * @return time as a long
	 */
	public long getTimeStamp(int _whichOne) {
		recordID = allRecordIDs[_whichOne];
		long ts = -2;
		if (useTimeStamp) {
			ts = getTimeStamp(rs, recordID);
		}
		return ts;
	}

	public boolean isTimeSTamping() {
		return useTimeStamp;
	}

	/*
	 * RecordFilter interface
	 */
	public boolean matches(byte[] candidate) {
		try {
			if (matchString == null)
				return true;
			String text = new String(candidate);
			return (text.indexOf(matchString) != -1);
		} catch (Exception e) {
			errorText = (e.getMessage() + "Matches" + candidate);
			return true;
		}
	}

	public void setMatchString(String _matchString) {
		matchString = _matchString;
	}

	public void setRecord(int _returnedRecordNumber, byte[] _bytes) {
		// _which = (getNumberOfRecords()-1) -_which;
		// System.out.println(_which + " " + recordID + " " + allRecordIDs.length);
		// recordID = allRecordIDs[_which];
		// problem here _which is recordID unabstracted form whichRecord the other are used to
		if (rs != null) {
			try {
				rs.setRecord(recordID, _bytes, 0, _bytes.length);

			} catch (RecordStoreNotOpenException e) {
				errorText = (e.getMessage() + "error");
			} catch (InvalidRecordIDException e) {
				errorText = (e.getMessage() + "error");
			} catch (RecordStoreFullException e) {
				errorText = (e.getMessage() + "error");
			} catch (RecordStoreException e) {
				errorText = (e.getMessage() + "error");
			}
		}
	}

	public void setRecord(int _which, String _text) {
		setRecord(_which, _text.getBytes());
	}

	public void setText(String _s) {

		if (recordID == -1) {
			recordID = addRecord(_s.getBytes());
			allRecordIDs = new int[] { recordID };
			// System.out.println("new" + recordID + "number of Records" + getNumberOfRecords());
		} else {
			// System.out.println("existing");
			setRecord(recordID, _s);
		}
	}

	public void setTimeStamping(boolean _isTS) {
		useTimeStamp = _isTS;
	}

}